//=====================================================================
// OdbgExt DBGSQL.CPP
//		Created by : Jeffda
//      Created on : 4/25/2008
//   Last Modified : 
//  This code adds SQL Ability to the debugger extention
//=====================================================================



#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <stdlib.h>
#include <dbghelp.h>
#include <dbgsql.h>
#include <wdbgexts.h>
#include <sql.h>

static HENV	lpEnv = NULL;
static HDBC	lpDbc = NULL;
static BOOL	bConnected = FALSE;

int dbgExecSQL(char *szInput, int showresults)
{
	
	HSTMT	lpStmt = NULL;
	static BOOL bConnected = FALSE;
	RETCODE		RetCode;
	SQLSMALLINT	sNumResults;


	// Allocate an environment
	if(!lpEnv)
	{  // if we don't have an  ENV Set set one up.
	
		if (SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&lpEnv) == SQL_ERROR)
		{
			fprintf(stderr,"Unable to allocate an environment handle\n");
			exit(-1);
		}

	// Register this as an application that expects 2.x behavior,
	// you must register something if you use AllocHandle

	TRYODBC(lpEnv,SQL_HANDLE_ENV,SQLSetEnvAttr(lpEnv,SQL_ATTR_ODBC_VERSION,(SQLPOINTER)SQL_OV_ODBC2, 0));

	// Allocate a connection

	}

	if(!bConnected)
	{
		TRYODBC(lpEnv,SQL_HANDLE_ENV,SQLAllocHandle(SQL_HANDLE_DBC,lpEnv,&lpDbc));

		
		// Connect to the driver.  Use the connection string if supplied
		// on the input, otherwise let the driver manager prompt for input.

		// This is hard coded to ODbgExt this needs to be changed to a reg config value.
		TRYCONNECTODBC(lpDbc,SQL_HANDLE_DBC,SQLDriverConnect(lpDbc, NULL,(SQLCHAR*) "DRIVER={SQL Server};SERVER=ODbgExt;Trusted_Connection=yes;DATABASE=ODbgExt", SQL_NTS,NULL, 0, NULL,	 SQL_DRIVER_NOPROMPT));
	}	
		

		dprintf("Connected!\n");

		if(!lpStmt)
		{
			TRYODBC(lpDbc,SQL_HANDLE_DBC,SQLAllocHandle(SQL_HANDLE_STMT,lpDbc,&lpStmt));
		}
		
	

		// Execute the query

		if (!szInput)
		{
			dprintf("No SQL COMMAND sent");
			return 0;
		}
		
		RetCode = SQLExecDirect(lpStmt,(SQLCHAR *)szInput,SQL_NTS);

		switch(RetCode)
		{
			case	SQL_SUCCESS_WITH_INFO:
			{
				HandleError(lpStmt,SQL_HANDLE_STMT,RetCode);
				// fall through
			}
			case	SQL_SUCCESS:
			{
				// If this is a row-returning query, display
				// results
				TRYODBC(lpStmt,	SQL_HANDLE_STMT,SQLNumResultCols(lpStmt,&sNumResults));

				if ((sNumResults > 0) & (showresults!=0))
				{
					DisplayResults(lpStmt,sNumResults);
				} else
				{
					SQLLEN		siRowCount;

					TRYODBC(lpStmt,	SQL_HANDLE_STMT,SQLRowCount(lpStmt,&siRowCount));

					if (siRowCount >= 0)
					{
						dprintf(TEXT("%d %s affected\n"),siRowCount,siRowCount == 1 ? TEXT("row") : TEXT("rows"));
					}
				}
				break;
			}

			case	SQL_ERROR:
			{
				HandleError(lpStmt,SQL_HANDLE_STMT,RetCode);
				break;
			}

			default:
					dprintf("Unexpected return code %d!\n",RetCode);

		}
		TRYODBC(lpStmt,	SQL_HANDLE_STMT,SQLFreeStmt(lpStmt,SQL_CLOSE));
		lpStmt = NULL;

		printf("SQL COMMAND>");


Exit:

	// Free ODBC handles and exit


	return 0;

}

/************************************************************************
/* DisplayResults: display results of a select query
/*
/* Parameters:
/* 		lpStmt		ODBC statement handle
/*		cCols		Count of columns
/************************************************************************/

void DisplayResults(HSTMT		lpStmt,			   		SQLSMALLINT	cCols)
{
	BINDING			*pFirstBinding, *pThisBinding;			
	SQLSMALLINT		siDisplaySize;
	RETCODE			RetCode;
	int ZTrue=1;

	// Allocate memory for each column 

	AllocateBindings(lpStmt,cCols,&pFirstBinding, &siDisplaySize);

	// Fetch and display the data
	DisplayTitles(lpStmt,siDisplaySize, pFirstBinding);

	do {
		// Fetch a row

		TRYODBC(lpStmt,SQL_HANDLE_STMT, RetCode = SQLFetch(lpStmt));
		if (RetCode == SQL_NO_DATA_FOUND)
			break;
			

		// Display the data.   Ignore truncations

		for (pThisBinding = pFirstBinding; pThisBinding; pThisBinding = pThisBinding->sNext)
		{
			if (pThisBinding->indPtr != SQL_NULL_DATA)
			{
				//_tprintf(pThisBinding->fChar ? TEXT(DISPLAY_FORMAT_C):  TEXT(DISPLAY_FORMAT),PIPE,pThisBinding->siDisplaySize,pThisBinding->siDisplaySize,pThisBinding->szBuffer);
				dprintf("%s",pThisBinding->szBuffer);
			
			} else
			{
				//_tprintf(TEXT(DISPLAY_FORMAT_C),PIPE,pThisBinding->siDisplaySize,pThisBinding->siDisplaySize,"<NULL>");
				dprintf("%s","<NULL>");
			
			}
		}
		dprintf(TEXT(" %c\n"),PIPE);
	} while (ZTrue);
/*
	SetConsole(siDisplaySize+2,TRUE);
	printf("%*.*s",siDisplaySize+2,siDisplaySize+2," ");
	SetConsole(siDisplaySize+2,FALSE);
	printf("\n");
*/

Exit:
	// Clean up the allocated buffers

	while (pFirstBinding)
	{
		pThisBinding=pFirstBinding->sNext;
		free(pFirstBinding->szBuffer);
		free(pFirstBinding);
		pFirstBinding=pThisBinding;
	}

}

/************************************************************************
/* AllocateBindings:  Get column information and allocate bindings
/* for each column.  
/*
/* Parameters:
/*		lpStmt		Statement handle
/*		cCols		Number of columns in the result set
/*		*lppBinding	Binding pointer (returned)
/*		lpDisplay	Display size of one line
/************************************************************************/

void AllocateBindings(HSTMT			lpStmt,
					  SQLSMALLINT	cCols,
					  BINDING		**lppBinding,
					  SQLSMALLINT	*lpDisplay)
{
	SQLSMALLINT		iCol;
	BINDING			*lpThisBinding, *lpLastBinding;
	SQLLEN	cchDisplay, ssType;
    SQLSMALLINT cchColumnNameLength;
	
	*lpDisplay = 0;
	
	lpLastBinding=0;

	for (iCol = 1; iCol <= cCols; iCol++)
	{
		lpThisBinding = (BINDING *)(malloc(sizeof(BINDING)));
		if (!(lpThisBinding))
		{
			dprintf("Out of memory!\n");
			exit(-100);
		}

		if (iCol == 1)
		{
			*lppBinding = lpThisBinding;
		}
		else
		{
			lpLastBinding->sNext = lpThisBinding;
		}
		lpLastBinding=lpThisBinding;


		// Figure out the display length of the column (we will
		// bind to char since we are only displaying data, in general
		// you should bind to the appropriate C type if you are going
		// to manipulate data since it is much faster...)
		
		TRYODBC(lpStmt,SQL_HANDLE_STMT,SQLColAttribute(lpStmt, iCol,  SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &cchDisplay));


		// Figure out if this is a character or numeric column; this is
		// used to determine if we want to display the data left- or right-
		// aligned.

		// !! Note a bug in the 3.x documentation.  We claim that 
		// SQL_DESC_TYPE is a 1.x feature.   That is not true, SQL_DESC_TYPE
		// is a 3.x feature.   SQL_DESC_CONCISE_TYPE maps to the 1.x 
		// SQL_COLUMN_TYPE.   This is what you must use if you want to work
		// against a 2.x driver.  Sorry for the inconvenience...

		TRYODBC(lpStmt,SQL_HANDLE_STMT,SQLColAttribute(lpStmt,iCol,SQL_DESC_CONCISE_TYPE,NULL,0,NULL,&ssType));


		lpThisBinding->fChar = (ssType == SQL_CHAR || ssType == SQL_VARCHAR || ssType == SQL_LONGVARCHAR);

		lpThisBinding->sNext = NULL;

		// Arbitrary limit on display size
		if (cchDisplay > DISPLAY_MAX)
			cchDisplay = DISPLAY_MAX;

		// Allocate a buffer big enough to hold the text representation
		// of the data.  Add one character for the null terminator

		lpThisBinding->szBuffer = (TCHAR *)malloc((cchDisplay+1) * sizeof(TCHAR));

		if (!(lpThisBinding->szBuffer))
		{
			dprintf("Out of memory!\n");
			exit(-100);
		}

		// Map this buffer to the driver's buffer.   At Fetch time,
		// the driver will fill in this data.  Note that the size is 
		// count of bytes (for Unicode).  All ODBC functions that take
		// SQLPOINTER use count of bytes; all functions that take only
		// strings use count of characters.
		SQLLEN *hack;
		hack = (SQLLEN *)&lpThisBinding->indPtr;
		
		TRYODBC(lpStmt,SQL_HANDLE_STMT,	SQLBindCol(lpStmt,  iCol,  SQL_C_TCHAR,(SQLPOINTER) lpThisBinding->szBuffer, (cchDisplay + 1) * sizeof(TCHAR), hack));

	   	
		// Now set the display size that we will use to display
		// the data.   Figure out the length of the column name

		TRYODBC(lpStmt,SQL_HANDLE_STMT,SQLColAttribute(lpStmt,iCol,SQL_DESC_NAME,NULL,0,&cchColumnNameLength,NULL));

		lpThisBinding->siDisplaySize = (SQLSMALLINT)max(cchDisplay, cchColumnNameLength);
		
		if (lpThisBinding->siDisplaySize < NULL_SIZE)
			lpThisBinding->siDisplaySize = NULL_SIZE;

		*lpDisplay += lpThisBinding->siDisplaySize + DISPLAY_FORMAT_EXTRA;
		
	}

	Exit:
		// Not really a good error exit handler, we should free
		// up any memory that we allocated.   But this is a sample...

		return;
}


/************************************************************************
/* DisplayTitles: print the titles of all the columns and set the 
/*			      shell window's width
/*
/* Parameters:
/*		lpStmt	  		Statement handle
/*		siDisplaySize	Total display size
/*		pBinding		list of binding information
/************************************************************************/

void	DisplayTitles(HSTMT		lpStmt,
					  DWORD		siDisplaySize,
					  BINDING	*pBinding)
{
	char szTitle[DISPLAY_MAX];
	SQLSMALLINT		iCol = 1;
	DWORD size;
	if(siDisplaySize)
	{
	  size=siDisplaySize;
	}

	for (;pBinding;pBinding=pBinding->sNext)
	{
		TRYODBC(lpStmt,	SQL_HANDLE_STMT,SQLColAttribute(lpStmt,	iCol++,SQL_DESC_NAME,szTitle,sizeof(szTitle),NULL,NULL));
		// bug bug we will need to use this later to get the title to display spacing pBinding->siDisplaySize, pBinding->siDisplaySize
		dprintf(" %s ",szTitle);

	}

Exit:

	dprintf(" %c",PIPE);
	dprintf("\n");

}


/************************************************************************
/* HandleError: display error information
/*
/* Parameters:
/*		hHandle		ODBC handle
/*		hType		Type of handle (HANDLE_STMT,HANDLE_ENV,HANDLE_DBC
/*		RetCode		Return code of failing command
/************************************************************************/

void HandleError(SQLHANDLE	hHandle,	
				SQLSMALLINT	hType,	
			    RETCODE	RetCode)
{
	SQLSMALLINT	iRec = 0;
	SQLINTEGER	iError;
	SQLCHAR     *pszMessage;
	char szState[SQL_SQLSTATE_SIZE + 1];
	

	if (RetCode == SQL_INVALID_HANDLE)
	{
		fprintf(stderr,"Invalid handle!\n");
		return;
	}
	
	pszMessage = (SQLCHAR *) malloc(1000 * sizeof(SQLCHAR));
	
	if(!pszMessage)
	{
		fprintf(stderr,"pszMessage allocation failed\n");
		return;	
	}

	while (SQLGetDiagRec(hType, hHandle, ++iRec, (SQLCHAR *)szState, &iError, pszMessage,(SQLSMALLINT)(1000 * sizeof(SQLCHAR)), (SQLSMALLINT *)NULL) == SQL_SUCCESS)
	{
		// Hide data truncated..
		if (strncmp(szState,"01004",5))
			dprintf("[%5.5s] %s (%d)\n",szState,pszMessage,iError);
	}
	
	free(pszMessage);
}

void sqlUninit(void)
{

	if (lpDbc)
	{
		SQLDisconnect(lpDbc);
		SQLFreeConnect(lpDbc);
		lpDbc=NULL;
		bConnected = FALSE;
	}
	if (lpEnv)
	{
		SQLFreeEnv(lpEnv);
		lpEnv=NULL;
	}	
		 
	dprintf("\nDisconnected.");
}